/**
 * Created by 断崖 on 2017/2/10.
 */
function Compile(el, vm) {
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    this.$vm = vm;

    if (this.$el) {
        this.$fragment = this.node2fragment(this.$el);
        this.compile(this.$fragment);
        this.$el.appendChild(this.$fragment);
    }
}

Compile.prototype = {
    compile: function (el) {
        var childNodes = el.childNodes,
            self = this;
        [].slice.call(childNodes).forEach(function (node) {
            var nodeText = node.textContent,
                reg = /\{\{(.*?)\}\}/;

            if (self.isElementNode(node)) {
                self.compileElement(node);
            } else if (self.isTextNode(node) && reg.test(nodeText)) {
                self.compileText(node, RegExp.$1);
            }

            if (node.childNodes && node.childNodes.length) {
                self.compile(node);
            }
        })
    },
    compileElement: function (el) {
        var nodeAttrs = el.attributes,
            self = this;

        [].slice.call(nodeAttrs).forEach(function (attr) {
            var attrName = attr.name,
                reg = /v-([^\:]+)\:?(.*)/,
                result = attrName.match(reg);
            if (result) {
                var exp = attr.value,
                    dir = result[1],
                    extra = result[2];

                Directive[dir] && Directive[dir](el, self.$vm, exp, extra, dir);

                el.removeAttribute(attrName);
            }
        })
    },
    compileText: function (el, exp) {
        Directive.nodeText(el, this.$vm, exp);
    },
    node2fragment: function (el) {
        var fragment = document.createDocumentFragment();
        while (el.firstChild) {
            fragment.appendChild(el.firstChild);
        }
        return fragment;
    },
    isElementNode: function (node) {
        return node.nodeType === 1;
    },
    isTextNode: function (node) {
        return node.nodeType === 3;
    }
};


var Directive = {
    $bind: function (node, vm, exp, extra, dir) {
        var updaterFn = Directive.$updater[dir];
        updaterFn && updaterFn(node, this.$getVMValue(vm, exp), null, extra);
        new Watcher(vm, exp, function (value, oldValue) {
            updaterFn && updaterFn(node, value, oldValue, extra);
        });
    },
    nodeText: function (node, vm, exp, extra) {
        this.$bind(node, vm, exp, extra, "nodeText");
    },
    text: function (node, vm, exp, extra) {
        this.$bind(node, vm, exp, extra, "text");
    },
    html: function (node, vm, exp, extra) {
        this.$bind(node, vm, exp, extra, "html");
    },
    model: function (node, vm, exp, extra) {
        this.$bind(node, vm, exp, extra, "model");
        
        var self = this,
            value = this.$getVMValue(vm, exp);
        node.addEventListener("input", function () {
            var newVal = this.value;
            if(newVal == value) return;
            self.$setVMValue(vm, exp, newVal);
            value = newVal;
        })
    },
    bind: function (node, vm, exp, extra) {
        this.$bind(node, vm, exp, extra, "bind");
    },
    on: function (el, vm, exp, eventType) {
        var eventFn = vm.$options.methods && vm.$options.methods[exp];
        if (eventType && eventFn) {
            el.addEventListener(eventType, eventFn.bind(vm), false);
        }
    },
    class: function (node, vm, exp, extra) {
        this.$bind(node, vm, exp, extra, "class");
    },
    $getVMValue: function (vm, exp) {
        var val = vm.$data;
        exp.split(".").forEach(function (key) {
            val = val[key];
        });
        return val;
    },
    $setVMValue: function (vm, exp, value) {
        var val = vm.$data;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
            if (i < exp.length - 1) {
                val = val[k];
            } else {
                val[k] = value;
            }
        });
    },
    $updater: {
        nodeText: function (node, value) {
            node.textContent = typeof value == 'undefined' ? '' : value;
        },
        text: function (node, value) {
            node.innerText = typeof value == 'undefined' ? '' : value;
        },
        html: function (node, value) {
            node.innerHTML = typeof value == 'undefined' ? '' : value;
        },
        bind: function (node, value, oldVal, extra) {
            if (extra && typeof extra == "string") {
                node.setAttribute(extra, value);
            }
        },
        model: function (node, value) {
            node.value = typeof value == 'undefined' ? "" : value;
        },
        class: function (node, value, oldValue) {
            var className = node.className;
            className = className.replace(oldValue, '').replace(/\s$/, '');
            var space = className && String(value) ? ' ' : '';
            node.className = className + space + value;
        }
    }
};